


"""
Event-driven framework of vn.py framework.
"""

from concurrent.futures import thread
import os
import threading
from collections import defaultdict
from queue import Queue, Empty
from threading import Thread
from time import sleep
from typing import Any, Callable, List
import redis
from redis import StrictRedis
import json

from .event import *

cfg.settings = {"redis": {
    "redis_addr": "localhost:6379",
    "host": "localhost",
    "port": 6379,
    "prefix_tick": "tick.",
    "prefix_bar": "bar.",
    "prefix_trade": "trade.",
    "prefix_position": "position.",
    "prefix_position_detail": "position_detail.",
    "prefix_order": "order.",
    "prefix_account": "acc.",
    "prefix_instrument": "instrument.",
    "prefix_future": "future.",
    "db":
        {
            "CS": 15,
            "MD": 1,
            "TD": 2,
            "STRA": 3,
            "DS": 5,
            "cs": 15,
            'md': 1,
            'td': 2,
            'stra': 3,
            "ds": 5}

}, }

prefix_tick = cfg.settings['redis']['prefix_tick']
prefix_bar = cfg.settings['redis']['prefix_bar']
prefix_account = cfg.settings['redis']['prefix_account']
prefix_position = cfg.settings['redis']['prefix_position']
prefix_position_detail = cfg.settings['redis']['prefix_position_detail']
prefix_trade = cfg.settings['redis']['prefix_trade']
prefix_order = cfg.settings['redis']['prefix_order']
prefix_instrument = cfg.settings['redis']['prefix_instrument']
prefix_future = cfg.settings['redis']['prefix_future']


class RedisDbType:
    CTP_TICK = 0
    CTP_ACC = 1
    CTP_TD = 2
    CONFIG = 15


class Event:
    """
    Event object consists of a type string which is used
    by event engine for distributing event, and a data
    object which contains the real data.
    """

    def __init__(self, event_type: str, data: Any = None):
        """"""
        self.event_type: str = event_type
        self.data: Any = data


# Defines handler function to be used in event engine.
HandlerType = Callable[[Event], None]


class BaseEventEngine:
    """"""

    def __init__(self, interval: int = 1, rds: StrictRedis = None):
        """
        Timer event is generated every 1 second by default, if
        interval not specified.
        """
        if rds is None:
            pool = redis.ConnectionPool(host="localhost", port=6379, db=0,
                                        decode_responses=True)
            rds = redis.StrictRedis(connection_pool=pool)
        self._interval: int = interval
        self._rds: StrictRedis = rds

        self._active: bool = False
        self._thread: Thread = Thread(target=self._run)
        self._timer: Thread = Thread(target=self._run_timer)
        self._handlers: defaultdict = defaultdict(list)
        self._general_handlers: List = []
        self._listened_channels = []
        self._listener = []
        self._ps = self._rds.pubsub()  # redis.pubsub()

        self._queue = Queue()

    def _run(self) -> None:
        """
        Get event from queue and then process it.
        """

        # self._ps.subscribe('config')

        while self._active:
            try:
                event = self._queue.get(block=True, timeout=1)
                self._process(event)
            except Empty:
                pass

            for data in self._ps.listen():
                if data['type'] in ['message', 'pmessage']:
                    ed = data['data']
                    if isinstance(ed, str):
                        ed = json.loads(ed)
                    threading.Thread(target=self._process, args=(Event(data['channel'], ed),)).start()

    def _process(self, event: Event) -> None:
        """
        First ditribute event to those handlers registered listening
        to this type.

        Then distrubute event to those general handlers which listens
        to all types.
        """
        if event.event_type in self._handlers:
            [handler(event) for handler in self._handlers[event.event_type]]

        if self._general_handlers:
            [handler(event) for handler in self._general_handlers]

    def _run_timer(self) -> None:
        """
        Sleep by interval second(s) and then generate a timer event.
        """
        while self._active:
            sleep(self._interval)
            event = Event(EVENT_TIMER)
            self.put(event)

    def put(self, event):
        self._queue.put(Event(event.event_type, event.data))

    def rds_put(self, event):
        self._rds.set(event.event_type, event.data)

    def start(self) -> None:
        """
        Start event engine to process events and generate timer events.
        """
        self._active = True
        self._thread.start()
        # self._timer.start()

    def stop(self) -> None:
        """
        Stop event engine.
        """
        self._active = False
        self._timer.join()
        self._thread.join()

    def publish(self, event: Event = None, event_type=None, data=None) -> None:
        """
        Publish an event object into redis server.
        """
        if event is None:
            self._rds.publish(event_type, json.dumps(data))
        else:
            self._rds.publish(event.event_type, json.dumps(event.data))

    def register(self, event_type: str, handler: HandlerType) -> None:
        """
        Register a new handler function for a specific event type. Every
        function can only be registered once for each event type.
        """
        handler_list = self._handlers[event_type]
        if handler not in handler_list:
            handler_list.append(handler)
            self._ps.subscribe(event_type)

    def unregister(self, event_type: str, handler: HandlerType) -> None:
        """
        Unregister an existing handler function from event engine.
        """
        handler_list = self._handlers[event_type]

        if handler in handler_list:
            handler_list.remove(handler)

        if not handler_list:
            self._handlers.pop(event_type)
            self._ps.unsuscribe(event_type)

    def register_general(self, handler: HandlerType) -> None:
        """
        Register a new handler function for all event types. Every
        function can only be registered once for each event type.
        """
        if handler not in self._general_handlers:
            self._general_handlers.append(handler)

    def unregister_general(self, handler: HandlerType) -> None:
        """
        Unregister an existing general handler function.
        """
        if handler in self._general_handlers:
            self._general_handlers.remove(handler)


class RedisEventEngine:
    """
    Event engine distributes event object based on its type
    to those handlers registered.

    It also generates timer event by every interval seconds,
    which can be used for timing purpose.
    """

    def __init__(self, interval: int = 1, rds: StrictRedis = None):
        """
        Timer event is generated every 1 second by default, if
        interval not specified.
        """
        if rds is None:
            pool = redis.ConnectionPool(host=cfg.settings['redis']['host'], port=cfg.settings['redis']['port'], db=0,
                                        decode_responses=True)
            rds = redis.StrictRedis(connection_pool=pool)
        self._interval: int = interval
        self._rds: StrictRedis = rds

        self._active: bool = False
        self._thread: Thread = Thread(target=self._run)
        self._timer: Thread = Thread(target=self._run_timer)
        self._handlers: defaultdict = defaultdict(list)
        self._general_handlers: List = []
        self._listened_channels = []
        self._listener = []
        self.ps = self._rds.pubsub()  # redis.pubsub()
        self.iids = {}  # iid:[eid,pid,need_subscribed,subscribed,main_second(0,1,2)]

        self._queue = Queue()

    def _run(self) -> None:
        """
        Get event from queue and then process it.
        """

        self.ps.subscribe('config')

        while self._active:
            for data in self.ps.listen():
                if data['type'] in ['message', 'pmessage']:
                    ed = data['data']
                    if isinstance(ed, str):
                        ed = json.loads(ed)
                    threading.Thread(target=self._process, args=(Event(data['channel'], ed),)).start()

    def _process(self, event: Event) -> None:
        """
        First ditribute event to those handlers registered listening
        to this type.

        Then distrubute event to those general handlers which listens
        to all types.
        """
        if event.event_type in self._handlers:
            [handler(event) for handler in self._handlers[event.event_type]]

        if self._general_handlers:
            [handler(event) for handler in self._general_handlers]

    def _run_timer(self) -> None:
        """
        Sleep by interval second(s) and then generate a timer event.
        """
        while self._active:
            sleep(self._interval)
            event = Event(EVENT_TIMER)
            self.put(event)

    def put(self, event):
        self._queue.put(Event(event.event_type, event.data))

    def rds_put(self, event):
        self._rds.set(event.event_type, event.data)

    def start(self) -> None:
        """
        Start event engine to process events and generate timer events.
        """
        self._active = True
        self._thread.start()
        # self._timer.start()

    def stop(self) -> None:
        """
        Stop event engine.
        """
        self._active = False
        self._timer.join()
        self._thread.join()

    def publish(self, event: Event = None, event_type=None, data=None) -> None:
        """
        Publish an event object into redis server.
        """
        if event is None:
            self._rds.publish(event_type, json.dumps(data))
        else:
            self._rds.publish(event.event_type, json.dumps(event.data))

    def register(self, event_type: str, handler: HandlerType) -> None:
        """
        Register a new handler function for a specific event type. Every
        function can only be registered once for each event type.
        """
        handler_list = self._handlers[event_type]
        if handler not in handler_list:
            handler_list.append(handler)
            self.ps.subscribe(event_type)

    def unregister(self, event_type: str, handler: HandlerType) -> None:
        """
        Unregister an existing handler function from event engine.
        """
        handler_list = self._handlers[event_type]

        if handler in handler_list:
            handler_list.remove(handler)

        if not handler_list:
            self._handlers.pop(event_type)
            self.ps.unsuscribe(event_type)

    def register_general(self, handler: HandlerType) -> None:
        """
        Register a new handler function for all event types. Every
        function can only be registered once for each event type.
        """
        if handler not in self._general_handlers:
            self._general_handlers.append(handler)

    def unregister_general(self, handler: HandlerType) -> None:
        """
        Unregister an existing general handler function.
        """
        if handler in self._general_handlers:
            self._general_handlers.remove(handler)

    def save_ss(self):
        """保存自选标的"""

    def load_ss(self):
        """载入自选标的"""

    def add2ss(self, iid):
        """加入自选"""

    def del_ss(self, iid):
        """从自选中删除"""

    def get_product_exchange_iid(self, data=None, pc='future'):
        """
        将 data 进行分组
        data: key:iid,value:{iid,eid,pid,...}
        按 ProductID，ExchangeID 进行
        """
        if not data:
            if pc == 'future':
                data = self.get_future()
            else:
                data = get_ctp_instrument_list()
        fd = data
        if not fd:
            return
        p_f = {}  # product-future
        e_f = {}  # exchange-future
        e_p = {}  # exchange-product
        f_p = {}
        f_e = {}
        for k, v in fd.items():
            pid = v['ProductID']
            eid = v['ExchangeID']
            f_p[k] = pid
            f_e[k] = eid
            self.iids[k] = [eid, pid, 0, 0, 0]
            if pid in p_f:
                p_f[pid].append(k)
            else:
                p_f[pid] = [k]
            if eid in e_f:
                e_f[eid].append(k)
            else:
                e_f[eid] = [k]
            if eid in e_p:
                e_p[eid].add(pid)
            else:
                e_p[eid] = {pid}

        if pc == 'future':
            cfg.product_future = p_f
            cfg.exchange_future = e_f
            cfg.exchange_product = e_p
            cfg.future_product = f_p
            if e_p:
                cfg.save_yml('exchange_product', e_p)
            if e_f:
                cfg.save_yml('exchange_future', e_f)
            if p_f:
                cfg.save_yml('product_future', p_f)
            if f_p:
                cfg.save_yml('future_product', f_p)
            if f_e:
                cfg.save_yml('future_exchange', f_e)

        else:
            cfg.product_instrument = p_f
            cfg.exchange_instrument = e_f
            cfg.exchange_product = e_p
            cfg.instruments_product = f_p
        return p_f, e_f

    def put_trade(self, data):
        put_ctp_trade_hash(data)

    def put_account(self, data):
        put_ctp_account_hash(data)

    def put_position(self, data):
        put_ctp_position_hash(data)

    def put_position_detail(self, data):
        put_ctp_position_detail_hash(data)

    def put_order(self, data):
        put_ctp_order_hash(data)

    def put_instrument(self, data):
        p_f, e_f = self.get_product_exchange_iid(data)
        self.instruments = data
        self.product_instrument = p_f
        self.exchange_instrument = e_f
        put_ctp_instrument_list(data)

    def put_future(self, data):
        # print(data)
        p_f, e_f = self.get_product_exchange_iid(data)
        self.futures = data
        self.product_future = p_f
        self.exchange_future = e_f
        put_ctp_future_list(data)

    def put_tick(self, data):
        put_ctp_tick(self._rds, data)

    def publish_tick(self, data):
        publish_ctp_tick(self._rds, data)

    def get_future(self):
        return get_ctp_future_list()

    def get_account(self):
        return get_ctp_account_hash()

    def get_order(self):
        return get_ctp_order_hash()

    def get_position(self):
        return get_ctp_position_hash()

    def get_position_detail(self):
        return get_ctp_position_detail_hash()

    def get_trade(self):
        return get_ctp_trade_hash()


def put_ctp_instrument_list(data, rds=cfg.td_rds):
    rds.rpush(EVENT_INSTRUMENT, json.dumps(data))


def get_ctp_instrument_list(rds=cfg.td_rds):
    data = rds.lrange(EVENT_INSTRUMENT, 0, -1)
    if not data:
        return

    return json.loads(data[0])


def get_ctp_future_list(rds=cfg.td_rds):
    data = rds.lrange(EVENT_FUTURE, 0, -1)

    if not data:
        return

    return json.loads(data[0])


def get_ctp_account_list(rds=cfg.td_rds, acc_id=''):
    data = rds.lrange(EVENT_ACCOUNT + acc_id, 0, -1)
    if not data:
        return None
    return json.loads(data[0])


def get_ctp_position_list(rds=cfg.td_rds, acc_id=''):
    data = rds.lrange(EVENT_POSITION + acc_id, 0, -1)

    if not data:
        return

    return [json.loads(data[i]) for i in range(len(data))]


def get_ctp_position_detail_list(rds=cfg.td_rds, acc_id=''):
    data = rds.lrange(EVENT_POSITION_DETAIL + acc_id, 0, -1)

    if not data:
        return

    return [json.loads(data[i]) for i in range(len(data))]


def get_ctp_trade_list(rds=cfg.td_rds, acc_id=''):
    data = rds.lrange(EVENT_TRADE + acc_id, 0, -1)

    if not data:
        return

    return [json.loads(data[i]) for i in range(len(data))]


def get_ctp_order_list(rds=cfg.td_rds, acc_id=''):
    data = rds.lrange(EVENT_ORDER + acc_id, 0, -1)

    if not data:
        return

    return [json.loads(data[i]) for i in range(len(data))]


def put_ctp_future_list(data, rds=cfg.td_rds):
    # print(data)
    rds.rpush(EVENT_FUTURE, json.dumps(data))
    cfg.save_yml('future', data)


def put_ctp_account_list(data, rds=cfg.td_rds, acc_id=''):
    rds.rpush(EVENT_ACCOUNT + acc_id, json.dumps(data))


def put_ctp_position_list(data, rds=cfg.td_rds, acc_id=''):
    rds.rpush(EVENT_POSITION + acc_id, json.dumps(data))


def put_ctp_position_detail_list(data, rds=cfg.td_rds, acc_id=''):
    rds.rpush(EVENT_POSITION_DETAIL + acc_id, json.dumps(data))


def put_ctp_trade_list(data, rds=cfg.td_rds, acc_id=''):
    rds.rpush(EVENT_TRADE + acc_id, json.dumps(data))


def put_ctp_order_list(data, rds=cfg.td_rds, acc_id=''):
    rds.rpush(EVENT_ORDER + acc_id, json.dumps(data))


def put_config(rds, data):
    rds.rpush("Config", json.dumps(data))


def put_ctp_tick(rds, data):
    rds.rpush(EVENT_TICK, json.dumps(data))


def publish_ctp_tick(rds, data):
    rds.publish(EVENT_TICK + data['InstrumentID'], json.dumps(data))


def put_hash(rds, name, *args, **kwargs):
    # print(f'{name}:\n{args}\n{kwargs}')
    if len(args) == 2:
        rds.hset(name, args)
    else:
        rds.hmset(name, kwargs)


def get_hash(rds, name, key=None):
    data = None

    if key and isinstance(key, str):
        data = rds.hget(name, key)
    elif key and isinstance(key, list):
        data = rds.hmget(name, key)
    else:
        data = rds.hgetall(name)
    # if name == EVENT_POSITION_DETAIL:
    # print(data)
    return {k: json.loads(data[k]) for k in data.keys()}


def put_ctp_account_hash(data, rds=cfg.td_rds, acc_id='', name=EVENT_ACCOUNT):
    acc_id = data.get('AccID', 'AccID') if not acc_id else acc_id
    rds.hset(name, acc_id, json.dumps(data))


def get_ctp_account_hash(rds=cfg.td_rds, acc_id='', name=EVENT_ACCOUNT):
    data = get_hash(rds, name, acc_id)
    if data:
        return data


def put_ctp_position_hash(data, rds=cfg.td_rds, pid='', name=EVENT_POSITION, acc_id=''):
    # print(f'put_ctp_position_hash:\n{data}')
    for k, v in data.items():
        rds.hset(name + acc_id, k, json.dumps(v))


def get_ctp_position_hash(rds=cfg.td_rds, pid='', name=EVENT_POSITION, acc_id=''):
    return get_hash(rds, name + acc_id, pid)


def put_ctp_position_detail_hash(data, rds=cfg.td_rds, pid='', name=EVENT_POSITION_DETAIL, acc_id=''):
    # print(data)
    rds.hset(name + acc_id, pid, json.dumps(data))


def get_ctp_position_detail_hash(rds=cfg.td_rds, pid='', name=EVENT_POSITION_DETAIL, acc_id=''):
    return get_hash(rds, name + acc_id, pid)


def put_ctp_order_hash(data, rds=cfg.td_rds, oid='', name=EVENT_ORDER, acc_id=''):
    if data:
        oid = data.get('OrderID', oid)
    rds.hset(name + acc_id, oid, json.dumps(data))


def get_ctp_order_hash(rds=cfg.td_rds, oid='', name=EVENT_ORDER):
    return get_hash(rds, name, oid)


def put_ctp_trade_hash(data, rds=cfg.td_rds, tid='', name=EVENT_TRADE, acc_id=''):
    if not tid:
        tid = data.get('TradeID', 'TradeID')
    rds.hset(name + acc_id, tid, json.dumps(data))


def get_ctp_trade_hash(rds=cfg.td_rds, tid='', name=EVENT_TRADE, acc_id=''):
    return get_hash(rds, name + acc_id, tid)


def put_stra_hash(stra_instance_id, rds=cfg.sd_rds, name=EVENT_STRA):
    rds.hset(name, stra_instance_id, 1)


def put_stra_signal_hash(stra_signal, stra_instance_id, rds=cfg.sd_rds, name=EVENT_STRA):
    rds.hset(name, stra_instance_id, stra_signal)


def publish_stra(stra_instance_id, rds=cfg.sd_rds):
    rds.publish(EVENT_STRA, stra_instance_id)


def publish_stra_signal(stra_instance_id, stra_signal, rds=cfg.sd_rds):
    rds.publish(EVENT_STRA + stra_instance_id, json.dumps(stra_signal))


def output(event):
    print('output', event.data)


def output1(event):
    print('output2', event.data)


class RE():
    def __init__(self):
        self._rds = cfg.dd_rds
        self.ps = self._rds.pubsub()
        self.hdl = {}
        threading.Thread(target=self.start, ).start()

    def start(self):
        while True:
            for data in self.ps.listen():
                print(data)
                msg = data['type']
                ch = data['channel']
                data = data['data']
                if msg == 'message':
                    self.process(ch, data)

    def process(self, ch, data):
        for fuc in self.hdl[ch]:
            fuc(Event(ch, data))

    def register(self, ch, handler):
        if ch not in self.hdl.keys():
            self.ps.subscribe(ch)
            self.hdl[ch] = [handler]
        else:
            self.hdl[ch].append(handler)

    def publish(self, e):
        self._rds.publish(e.event_type, e.data)





class StraRedisEventEngine(BaseEventEngine):
    """基于 Redis 事件的策略引擎
    1、 策略启动时，publish(EVENT_STRA,stra_instance_id), 以供订阅
    2、 策略生成信号时， publish(EVENT_STRA_SIGNAL+stra_instance_id, new_signal), 以供订阅， 并保存 save_signal
    3、 策略初始化时，调入数据： load_bar, load_tick
    """

    def __init__(self, rds):
        super(StraRedisEventEngine, self).__init__(rds=rds)
        self.db = database_manager
        self._tick_ps = self._rds.pubsub()
        self._bar_ps = self._rds.pubsub()
        self.strategies = {}
        self.symbol_tick_strategy_map = {}  # symbol:stra_name
        self.symbol_bar_strategy_map = {}  # symbol:stra_name
        self._tick_thrd = threading.Thread(target=self._start_tick_pubsub)
        self._bar_thrd = threading.Thread(target=self._start_bar_pubsub)

    def _start_tick_pubsub(self):
        while self._active:
            for data in self._tick_ps.listen():
                if data['type'] in ['message', 'pmessage']:
                    ed = json.loads(data['data'])
                    # if isinstance(ed, str):
                    #     ed = json.loads(ed)
                    threading.Thread(target=self._process_tick, args=(ed,)).start()

    def _process_tick(self, tick):
        symbol = tick['InstrumentID']

    def _start_bar_pubsub(self, instrument=None):
        if instrument:
            self._bar_ps.subscribe(EVENT_TICK + instrument)
        while self._active:
            for data in self._bar_ps.listen():
                if data['type'] in ['message', 'pmessage']:
                    ed = json.loads(data['data'])
                    # if isinstance(ed, str):
                    #     ed = json.loads(ed)
                    threading.Thread(target=self._process_bar, args=(ed,)).start()

    def _process_bar(self, bar):
        symbol = bar['InstrumentID']

    def publish_signal(self, stra_instance_id, signal):
        """策略生成信号时执行， 由策略激发
        1、发送本信号,publish(EVENT_STRA_SIGNAL+stra_instance_id,signal)
        2、保存信号
        :param stra_instance_id: 策略实例ID
        :param signal: 策略信号，
        """
        et = EVENT_STRA_SIGNAL + stra_instance_id
        ed = signal.__dict__
        # print(et, ed)
        self.publish(event_type=et, data=ed)

    def publish_strategy_init(self, stra_instance_id):
        """策略实例初始化完成后由策略激发, 要订阅相应的 instrument的 tick 或者 bar
           publish(EVENT_STRA,stra_instance_id)
        """

    def publish_stop_signal(self, stra_instance_id, stop_entity):
        """当策略的止损信号发生时由策略激发, 供 Account 订阅

           publish(stra_instance_id)
        """
        self.publish(event_type=EVENT_STOP_ORDER + stra_instance_id, data=stop_entity.__dict__)

    def on_strategy_start(self, stra_instance_id):
        """策略启动后调用"""

    def load_bar(self, symbol, start_datetime, end_datetime, intervals=1):
        """从数据库中调入数据"""
        bars = self.db.load_bar_data(symbol, '', intervals, start_datetime, end_datetime)
        return bars

    def load_tick(self, symbol, start_datetime, end_datetime):
        """从数据库中调入数据"""
        ticks = self.db.load_tick_data(symbol, '', start_datetime, end_datetime)
        return ticks

    def register_strategy(self, strategy):
        """注册已经创建好的策略"""
        self.strategies[strategy.stra_name] = strategy

    def load_strategy(self, stra_conf='StraConf'):
        """根据策略配置创建并注册策略
        stra_conf的结构见：entity.stra_entity.StraConf
            stra_class: str = ''
    stra_file: str = ''
    symbol: str = ''
    interval_type = 0
    intervals = 0
    start_datetime = ''
    variables = {}
    params = {}
    other_data = []
        """
        """通过文件名取到对应的继承Data的类并实例"""
        stra_file = stra_conf.stra_file
        stra_class = stra_conf.stra_class
        if stra_file:  # in stra_conf.cfg.strategy_name
            f = os.path.join('./strategies', f'{stra_file}.py')
            # 只处理对应的 py文件
            if not os.path.exists(f):
                stra_conf.cfg.log.info(f'{f} is not exists!')
                return f'{f} is not exists!'
            # 以目录结构作为 namespace
            module_name = f"{os.path.split(os.path.dirname(f))[1]}.{stra_class}"
            module = __import__(module_name)  # import module

            c = getattr(getattr(module, stra_class), stra_class)  # 双层调用才是class,单层是为module

    def start(self) -> None:
        self._active = True
        self._tick_thrd.start()
        self._bar_thrd.start()

    def stop(self):
        self._active = False


class BackTestEngine(StraRedisEventEngine):
    def __init__(self, rds):
        """"""
        super(BackTestEngine, self).__init__(rds)

    def publish_signal(self, stra_instance_id, signal):
        """策略生成信号时执行， 由策略激发
        1、发送本信号,publish(EVENT_STRA_SIGNAL+stra_instance_id,signal)
        2、保存信号
        :param signal: 策略信号，
        """
        et = EVENT_STRA_SIGNAL + stra_instance_id
        ed = signal.__dict__
        print(et, ed)


if __name__ == "__main__":
    from BQ.utils import cfg
    from BQ.ss.strategies.pvc import StraPVC

    ree = StraRedisEventEngine(cfg.sd_rds)
    pvc = StraPVC(ree, 'pvc', 'TA201', None)
    ticks = ree.load_tick('v2201', '2021-12-08', None)
    pvc.on_ticks(ticks)
    for i in range(len(ticks)):
        pvc.on_tick(ticks.iloc[i])
    print(ticks.tail())
